﻿using System;
using System.Net.Sockets;
using System.Timers;
using System.Windows;
using System.Windows.Media;
using TS3QueryLib.Core;
using TS3QueryLib.Core.Query;
using TS3QueryLib.Core.Query.HelperClasses;
using TS3QueryLib.Core.Query.Responses;
using TS3ServerManager.BL;

namespace TS3ServerManager.Controls
{
	/// <summary>
	/// Interaction logic for ServerControl.xaml
	/// </summary>
	public partial class ServerControl
	{
		private AsyncTcpDispatcher SocketDispatcher { get; set; }
		private QueryRunner QueryRunner { get; set; }
		private Timer KeepConnectedTimer { get; set; }
		private ServerConnectInfo ConnectionInfo { get; set; }

		public ServerControl()
		{
			InitializeComponent();
			Loaded += ServerControl_Loaded;
		}

		private void ServerControl_Loaded(object sender, RoutedEventArgs e)
		{
			if (IsLoaded)
				return;

			KeepConnectedTimer = new Timer(TimeSpan.FromSeconds(30).TotalMilliseconds);
			KeepConnectedTimer.Elapsed += KeepConnectedTimer_Elapsed;
			ConnectionInfo = ((ServerConnectInfo) DataContext).Clone();

			Connect();
		}

		private void Connect()
		{
			SocketDispatcher = new AsyncTcpDispatcher(ConnectionInfo.Host, ConnectionInfo.QueryPort);
			SocketDispatcher.ReadyForSendingCommands += SocketDispatcher_ReadyForSendingCommands;
			SocketDispatcher.ServerClosedConnection += SocketDispatcher_ServerClosedConnection;
			SocketDispatcher.SocketError += SocketDispatcher_SocketError;
			SocketDispatcher.BanDetected += SocketDispatcher_BanDetected;

			QueryRunner = new QueryRunner(SocketDispatcher);
			QueryRunner.UnknownNotificationReceived += QueryRunner_UnknownNotificationReceived;

			RunOnUIThread(() => ToggleUserInterfaceConnectionState(ConnectionState.Connecting));

			try
			{
				SocketDispatcher.Connect();
			}
			catch (SocketException ex)
			{
				ToggleUserInterfaceConnectionState(ConnectionState.Disconnected);
				MessageBox.Show("Error occurred during connect: " + ex.Message);
				return;
			}
		}

		private void SocketDispatcher_BanDetected(object sender, TS3QueryLib.Core.Common.EventArgs<SimpleResponse> e)
		{
			RunOnUIThread(() => MessageBox.Show("You were banned from the server: " + e.Value.BanExtraMessage));
		}

		private void Disconnect()
		{
			if (QueryRunner == null)
				return;

			QueryRunner.Dispose();
		}

		private void KeepConnectedTimer_Elapsed(object sender, ElapsedEventArgs e)
		{
			if (SocketDispatcher == null || !SocketDispatcher.IsConnected || QueryRunner == null)
				return;

			RunIfNotDisposed(() => QueryRunner.GetVersion());
		}

		private void SocketDispatcher_ServerClosedConnection(object sender, EventArgs e)
		{
			RunOnUIThread(() => ToggleUserInterfaceConnectionState(ConnectionState.Disconnected));
			SetLastAction("Connection to server lost..");
		}

		private void SocketDispatcher_SocketError(object sender, TS3QueryLib.Core.Communication.SocketErrorEventArgs e)
		{
			switch (e.SocketError)
			{
				case SocketError.ConnectionReset:
					break;
				case SocketError.TimedOut:
					string timeoutMessage = string.Format("Connection to '{0}' at query port '{1}' timed out.", SocketDispatcher.Host, SocketDispatcher.Port);
					MessageBox.Show(timeoutMessage);
					SetLastAction(timeoutMessage);
					break;
				default:
					string unhandledErrorMessage = "Unhandled socket error occured: " + e.SocketError;
					MessageBox.Show(unhandledErrorMessage);
					SetLastAction(unhandledErrorMessage);
					break;
			}

			RunOnUIThread(() => ToggleUserInterfaceConnectionState(ConnectionState.Disconnected));
		}

		private void QueryRunner_UnknownNotificationReceived(object sender, TS3QueryLib.Core.Common.EventArgs<string> e)
		{
			MainArea.ShowNotification(e.Value);
		}

		private void SocketDispatcher_ReadyForSendingCommands(object sender, EventArgs e)
		{
			RunOnUIThread(() => ToggleUserInterfaceConnectionState(ConnectionState.Connected));
			SetLastAction(string.Format("Connected to '{0}' at query port '{1}'.", SocketDispatcher.Host, SocketDispatcher.Port));

			if (ConnectionInfo != null && ConnectionInfo.Username != null)
			{
				RunIfNotDisposed(() =>
				{
					SimpleResponse loginResponse = QueryRunner.Login(ConnectionInfo.Username, ConnectionInfo.Password);

					if (loginResponse.IsErroneous)
						MessageBox.Show("Login failed: " + loginResponse.ErrorMessage);
				});
			}

			ushort voicePort = 0;
			if (ConnectionInfo != null && ConnectionInfo.VoicePort.HasValue)
			{
				voicePort = ConnectionInfo.VoicePort.Value;
			}
			else
			{
				RunIfNotDisposed(() =>
				{
					ListResponse<ServerListItem> serverListResponse = QueryRunner.GetServerList();

					if (serverListResponse.IsErroneous || serverListResponse.Values.Count == 0)
						return;

					voicePort = serverListResponse.Values[0].ServerPort;
				});
			}

			RunIfNotDisposed(() =>
			{
				QueryRunner.SelectVirtualServerByPort(voicePort);
				RunIfNotDisposed(() => QueryRunner.UpdateCurrentQueryClient(new ClientModification { Nickname = string.Format("TS3 Server Manager #{0}", new Random().Next(1, 1000)) }));
				RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.Server));
				RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.TextChannel));
				RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.TextPrivate));
				RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.TextServer));
				RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.TokenUsed));

				RunIfNotDisposed(() =>
				{
					WhoAmIResponse whoAmIResponse = QueryRunner.SendWhoAmI();

					if (!whoAmIResponse.IsErroneous)
						RunIfNotDisposed(() => QueryRunner.RegisterForNotifications(ServerNotifyRegisterEvent.Channel, whoAmIResponse.ChannelId));
				});
			});
		}

		private void ToggleUserInterfaceConnectionState(ConnectionState state)
		{
			KeepConnectedTimer.Enabled = state == ConnectionState.Connected;
			ConnectionStateEllipse.Fill = state == ConnectionState.Disconnected ? Brushes.Red : (state == ConnectionState.Connecting) ? Brushes.Yellow : Brushes.Lime;
			ConnectionStateButton.IsEnabled = state == ConnectionState.Connected;

			if (state == ConnectionState.Connected)
				MainArea.ClearNotifications();
		}

		private void ConnectionStateButton_Click(object sender, RoutedEventArgs e)
		{
			Disconnect();
		}

		private void SetLastAction(string message)
		{
			RunOnUIThread(() => LastStatusMessageeTextBlock.Text = string.Format("[{0}] {1}", DateTime.Now.ToString("hh:mm:ss"), message));
		}

		private void RunOnUIThread(Action logic)
		{
			if (Dispatcher.CheckAccess())
				logic();
			else
				Dispatcher.Invoke(logic);
		}

		private void RunIfNotDisposed(Action logic)
		{
			if (QueryRunner.IsDisposed)
				return;

			logic();
		}

		public void Dispose()
		{
			try
			{
				if (QueryRunner != null)
					QueryRunner.Dispose();
			}
			catch (Exception)
			{
				
			}
		}
	}
}
